// Tierless
// Copyright © 2010 Alphora
// This file is licensed under a modified BSD-license (see tierless_license.txt).
using System;
using System.Collections.Generic;
using Tierless.Framework.Utilities;

namespace Tierless.Framework.EntityTypes
{
	/// <summary>
	/// Indicates that the assembly in which it appears contains Tierless entity type model classes.
	/// </summary>
	[AttributeUsage(AttributeTargets.Assembly)]
	public sealed class EntityTypeAssemblyAttribute : Attribute
	{}

	/// <summary>
	/// Indicates that the type on which it appears is an entity type model.
	/// </summary>
	[AttributeUsage(AttributeTargets.Class, Inherited = true)]
	public sealed class EntityTypeAttribute : Attribute
	{
		/// <summary>
		/// Initializes a new instance of the EntityTypeAttribute class.
		/// </summary>
		/// <param name="entityTypeName">The name of the entity type.</param>
		public EntityTypeAttribute(string entityTypeName)
		{
			EntityTypeName = entityTypeName;
		}

		/// <summary>
		/// Gets the name of the entity type.
		/// </summary>
		public string EntityTypeName { get; private set; }
	}

	/// <summary>
	/// Indicates that the type on which it appears provides the default implementation for a specific column of an entity type.
	/// </summary>
	[AttributeUsage(AttributeTargets.Class)]
	public sealed class EntityColumnDefaultAttribute : Attribute
	{
		/// <summary>
		/// Initializes a new instance of the EntityColumnDefaultAttribute class.
		/// </summary>
		/// <param name="entityTypeName">The name of the entity type to which the default should apply.</param>
		/// <param name="columnName">The name of the column to which the default should apply.</param>
		public EntityColumnDefaultAttribute(string entityTypeName, string columnName)
		{
			EntityTypeName = entityTypeName;
			ColumnName = columnName;
		}

		/// <summary>
		/// Gets the name of the entity type to which the default should apply.
		/// </summary>
		public string EntityTypeName { get; private set; }

		/// <summary>
		/// Gets the name of the column to which the default should apply.
		/// </summary>
		public string ColumnName { get; private set; }
	}

	/// <summary>
	/// Indicates the default value for columns of the entity type on which it appears.
	/// </summary>
	[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
	public sealed class EntityColumnDefaultValueAttribute : Attribute
	{
		/// <summary>
		/// Initializes a new instance of the EntityColumnDefaultValueAttribute class.
		/// </summary>
		/// <param name="columnName">The name of the column to which the default applies.</param>
		/// <param name="defaultValue">The value for the default.</param>
		public EntityColumnDefaultValueAttribute(string columnName, object defaultValue)
		{
			ColumnName = columnName;
			DefaultValue = defaultValue;
		}

		/// <summary>
		/// Gets the name of the column to which the default applies.
		/// </summary>
		public string ColumnName { get; private set; }

		/// <summary>
		/// Gets the value for the default.
		/// </summary>
		public object DefaultValue { get; private set; }
	}

	/// <summary>
	/// Indicates that the class on which it appears provides constraint registration for a given entity type.
	/// </summary>
	[AttributeUsage(AttributeTargets.Class)]
	public sealed class EntityConstraintsAttribute : Attribute
	{
		/// <summary>
		/// Initializes a new instance of the EntityConstraintAttribute class.
		/// </summary>
		/// <param name="entityTypeName">The name of the entity type.</param>
		public EntityConstraintsAttribute(string entityTypeName)
		{
			EntityTypeName = entityTypeName;
		}

		/// <summary>
		/// Gets the name of the entity type.
		/// </summary>
		public string EntityTypeName { get; private set; }
	}

	/// <summary>
	/// Provides a factory for obtaining entity type information.
	/// </summary>
	public static class EntityTypeFactory
	{
		private static bool _isPopulated;

		private static Dictionary<string, Type> _entityTypes = new Dictionary<string, Type>();

		private static Dictionary<string, Type> _entityConstraints = new Dictionary<string, Type>();

		private static Dictionary<string, Dictionary<string, Type>> _entityColumnDefaults = new Dictionary<string, Dictionary<string, Type>>();

		private static void EnsurePopulated()
		{
			lock (_entityTypes)
			{
				if (!_isPopulated)
				{
					foreach (var assembly in AssemblyUtility.GetAssemblies())
						if (Attribute.IsDefined(assembly, typeof (EntityTypeAssemblyAttribute)))
							foreach (var type in assembly.GetTypes())
							{
								var attribute = Attribute.GetCustomAttribute(type, typeof (EntityTypeAttribute)) as EntityTypeAttribute;
								if (attribute != null)
									_entityTypes.Add(attribute.EntityTypeName, type);

								var columnDefaultAttribute = Attribute.GetCustomAttribute(type, typeof (EntityColumnDefaultAttribute)) as EntityColumnDefaultAttribute;
								if (columnDefaultAttribute != null)
								{
									Dictionary<string, Type> columnDefaults;
									if (!_entityColumnDefaults.TryGetValue(columnDefaultAttribute.EntityTypeName, out columnDefaults))
									{
										columnDefaults = new Dictionary<string, Type>();
										_entityColumnDefaults.Add(columnDefaultAttribute.EntityTypeName, columnDefaults);
									}

									columnDefaults.Add(columnDefaultAttribute.ColumnName, type);
								}

								var constraintsAttribute = Attribute.GetCustomAttribute(type, typeof (EntityConstraintsAttribute)) as EntityConstraintsAttribute;
								if (constraintsAttribute != null)
									_entityConstraints.Add(constraintsAttribute.EntityTypeName, type);
							}

					_isPopulated = true;
				}
			}
		}

		/// <summary>
		/// Creates the <see cref="EntityType"/> describing the entity type with the given name.
		/// </summary>
		/// <param name="entityTypeName">The name of the entity type.</param>
		/// <returns>The entity type information.</returns>
		public static EntityType Create(string entityTypeName)
		{
			EnsurePopulated();

			lock (_entityTypes)
			{
				EntityType entityType;

				Type entityTypeType;
				_entityTypes.TryGetValue(entityTypeName, out entityTypeType);
				if (entityTypeType != null)
					entityType = (EntityType) Activator.CreateInstance(entityTypeType, entityTypeName);
				else
					entityType = new EntityType(entityTypeName);

				Type entityConstraintsType;
				_entityConstraints.TryGetValue(entityTypeName, out entityConstraintsType);
				if (entityConstraintsType != null)
				{
					var methodInfo = entityConstraintsType.GetMethod("Register", new[] { typeof (EntityType) });
					if (methodInfo != null)
						methodInfo.Invoke(null, new object[] { entityType });
				}

				return entityType;
			}
		}

		/// <summary>
		/// Retrieves a dictionary of the entity column defaults defined for a given entity type.
		/// </summary>
		/// <param name="entityTypeName">The name of the entity type.</param>
		/// <returns>A dictionary of the entity column defaults defined for a given entity type.</returns>
		public static Dictionary<string, EntityTypeColumnDefault> GetEntityDefaultTypes(string entityTypeName)
		{
			EnsurePopulated();

			Dictionary<string, Type> defaultTypes;
			var defaults = new Dictionary<string, EntityTypeColumnDefault>();
			if (_entityColumnDefaults.TryGetValue(entityTypeName, out defaultTypes))
				foreach (var entry in defaultTypes)
					defaults.Add(entry.Key, (EntityTypeColumnDefault) Activator.CreateInstance(entry.Value));

			return defaults;
		}

		/// <summary>
		/// Retrieves a dictionary of the entity column default values defined for a given entity type.
		/// </summary>
		/// <param name="entityTypeName">The name of the entity type.</param>
		/// <returns>A dictionary of the entity column default values defined for a given entity type.</returns>
		public static Dictionary<string, object> GetEntityDefaultValues(string entityTypeName)
		{
			EnsurePopulated();

			Type entityTypeType;
			var defaultValues = new Dictionary<string, object>();
			if (_entityTypes.TryGetValue(entityTypeName, out entityTypeType))
			{
				var defaultValueAttributes = Attribute.GetCustomAttributes(entityTypeType, typeof (EntityColumnDefaultValueAttribute));
				if (defaultValueAttributes.Length > 0)
					foreach (EntityColumnDefaultValueAttribute defaultValueAttribute in defaultValueAttributes)
						defaultValues.Add(defaultValueAttribute.ColumnName, defaultValueAttribute.DefaultValue);
			}

			return defaultValues;
		}
	}
}